{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "import argparse\n",
    "from typing import Union, List\n",
    "\n",
    "import cv2\n",
    "import numpy\n",
    "import scipy\n",
    "from matplotlib import pyplot as plt\n",
    "from matplotlib.patches import Circle\n",
    "from scipy import ndimage\n",
    "\n",
    "from demo import Demonstration\n",
    "from ply import write_xyz_rgb_as_ply, Ply\n",
    "from trainer import Trainer\n",
    "\n",
    "%matplotlib inline"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "def read_image(path: Union[str, List[str]], img_type: str, history=False):\n",
    "    \"\"\"\n",
    "    Reads image into numpy array\n",
    "    @param path: Path to image\n",
    "    @param img_type: One of 'color', 'depth'\n",
    "    @param history: Whether or not to read history for depth images\n",
    "    @return: Array containing image contents\n",
    "    \"\"\"\n",
    "    # This is repeated several times in the code and should ideally be refactored into a function\n",
    "\n",
    "    if img_type == \"color\":\n",
    "        return cv2.cvtColor(cv2.imread(path), cv2.COLOR_BGR2RGB)\n",
    "    elif img_type == \"depth\":\n",
    "        if history:\n",
    "            return numpy.stack([cv2.imread(file, -1).astype(numpy.float32)/100000 for file in path], axis=-1)\n",
    "        else:\n",
    "            return numpy.stack([cv2.imread(path, -1)]*3, axis=-1).astype(numpy.float32)/100000\n",
    "    return None"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "# Workspace limits\n",
    "workspace_limits = numpy.asarray([[-0.724, -0.276], [-0.224, 0.224], [-0.0001, 0.5]])\n",
    "\n",
    "# Test images\n",
    "test_color_heightmap = read_image(\"logs/2020-06-07-16-31-33_Real-Stack-Two-Step-Reward-Masked-Testing/data/color-heightmaps/000004.0.color.png\", 'color')\n",
    "test_depth_heightmap = read_image([\"logs/2020-06-07-16-31-33_Real-Stack-Two-Step-Reward-Masked-Testing/data/depth-heightmaps/000004.0.depth.png\",\n",
    "                                   \"logs/2020-06-07-16-31-33_Real-Stack-Two-Step-Reward-Masked-Testing/data/depth-heightmaps/000003.0.depth.png\",\n",
    "                                   \"logs/2020-06-07-16-31-33_Real-Stack-Two-Step-Reward-Masked-Testing/data/depth-heightmaps/000002.0.depth.png\"], 'depth', True)\n",
    "\n",
    "print(test_depth_heightmap.shape)"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%% Read test color and depth height maps\n"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "stack_snapshot_file = 'logs/base_models/rows_hist_densenet/snapshot.reinforcement_action_efficiency_best_value.pth'\n",
    "# stack_snapshot_file = 'logs/base_models/stacking_hist_densenet/snapshot.reinforcement_action_efficiency_best_value.pth'\n",
    "# stack_snapshot_file = 'logs/base_models/unstacking_hist_densenet/snapshot.reinforcement_action_efficiency_best_value.pth'\n",
    "# stack_snapshot_file = 'logs/base_models/vertical_square_hist_densenet/snapshot.reinforcement_trial_success_rate_best_value.pth'\n",
    "policy_name = 'row'\n",
    "# policy_name = 'stack'\n",
    "# policy_name = 'unstack'\n",
    "# policy_name = 'square'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "# Demo files\n",
    "demo = Demonstration(\"logs/demos/vertical_square_demos\", 1, None)\n",
    "demo_color_heightmap, demo_depth_heightmap = demo.get_heightmaps(\"grasp\", 12, use_hist=True)\n",
    "\n",
    "\n",
    "\n",
    "stack_trainer = Trainer(method='reinforcement', push_rewards=True, future_reward_discount=0.5,\n",
    "                        is_testing=True, snapshot_file=stack_snapshot_file,\n",
    "                        force_cpu=False, goal_condition_len=0, place=True,\n",
    "                        pretrained=True, flops=False, network='densenet',\n",
    "                        common_sense=True, place_common_sense=True,\n",
    "                        show_heightmap=False, place_dilation=0.01,\n",
    "                        common_sense_backprop=True, trial_reward='spot',\n",
    "                        num_dilation=0)"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "# get demo action index vector\n",
    "action_vector = demo.action_dict[3][1]\n",
    "\n",
    "# convert rotation angle to index\n",
    "best_rot_ind = numpy.around((numpy.rad2deg(action_vector[-2]) % 360) * 16 / 360).astype(int)\n",
    "# test_rot_ind = 15\n",
    "\n",
    "# convert robot coordinates to pixel\n",
    "workspace_pixel_offset = workspace_limits[:2, 0] * -1 * 1000\n",
    "best_action_xy = ((workspace_pixel_offset + 1000 * action_vector[:2]) / 2).astype(int)\n",
    "\n",
    "print(best_action_xy)\n",
    "print(best_rot_ind)"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%% Compute index of demo action\n"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "_, demo_features, _ = stack_trainer.forward(demo_color_heightmap, demo_depth_heightmap,\n",
    "                                            is_volatile=True, keep_action_feat=True, demo_mask=True)[:3]\n",
    "demo_features = demo_features.filled(0.0)"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%% Compute demo features\n"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "_, test_features , _ = stack_trainer.forward(test_color_heightmap, test_depth_heightmap,\n",
    "                                            is_volatile=True, keep_action_feat=True, demo_mask=True)[:3]\n",
    "test_features = test_features.filled(0.0)\n"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%% Compute test features\n"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "demo_features_rot = demo_features[best_rot_ind,:,:,:]\n",
    "# test_features_rot = test_features[test_rot_ind,:,:,:]\n",
    "\n",
    "# Compute mask\n",
    "demo_mask = (demo_features_rot == 0).all(axis=0)\n",
    "# test_mask = (test_features_rot == 0).all(axis=0)\n",
    "test_mask = (test_features==0).all(axis=1)"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%% Compute rematch distance\n"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "# match_dist = numpy.zeros([16, 224, 224])\n",
    "match_dist = numpy.sum(numpy.square(numpy.expand_dims(demo_features_rot[:, best_action_xy[1], best_action_xy[0]],(0,2,3)) - test_features), axis=1)\n",
    "match_dist[test_mask] = numpy.max(match_dist)\n",
    "matched_action = numpy.unravel_index(numpy.argmin(match_dist), (16, 224, 224))\n",
    "\n",
    "test_rot_ind = matched_action[0]\n",
    "test_features_rot = test_features[test_rot_ind,:,:,:]\n",
    "test_mask_rot = test_mask[test_rot_ind,:,:]"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%% Compute match distance\n"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "fig, ax = plt.subplots(1)\n",
    "ax.imshow(match_dist[test_rot_ind,:,:], cmap=\"gray\")\n",
    "\n",
    "circle = Circle((matched_action[2], matched_action[1]))\n",
    "ax.add_patch(circle)\n",
    "print(matched_action)"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%% Plot match distance\n"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "rematch_squared_distance = numpy.zeros([224, 224])\n",
    "\n",
    "for i in range(0, 224):\n",
    "    for j in range(0, 224):\n",
    "        x = numpy.expand_dims(test_features_rot[:,i,j], (1,2))\n",
    "        displacement = demo_features_rot - x\n",
    "        distances = numpy.sum(numpy.square(displacement), axis=0)\n",
    "        distances = ndimage.gaussian_filter(distances, sigma=(3,3))\n",
    "        distances[demo_mask] = numpy.max(distances) * 1.1\n",
    "        match_index = numpy.unravel_index(numpy.argmin(distances), (224, 224))\n",
    "        rematch_squared_distance[i,j] = numpy.sum(numpy.square(match_index - best_action_xy[[1, 0]]))\n",
    "\n",
    "rematch_distance = numpy.sqrt(rematch_squared_distance)"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n",
     "is_executing": true
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "fig, ax = plt.subplots(1)\n",
    "\n",
    "ax.imshow(demo_color_heightmap)\n",
    "circle = Circle(best_action_xy)\n",
    "ax.add_patch(circle)\n",
    "# fig, ax = plt.imshow(demo_color_heightmap)\n"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%% Generate demo plot\n",
     "is_executing": true
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "plt.imshow(test_color_heightmap)"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%% Generate test plot\n",
     "is_executing": true
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "plt.imshow(demo_mask, cmap='gray')"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n",
     "is_executing": true
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "plt.imshow(test_mask_rot, cmap='gray')"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n",
     "is_executing": true
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "rematch_distance[test_mask_rot] = numpy.max(rematch_distance)\n",
    "plt.imshow(rematch_distance, cmap='gray')"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%% Rematch plot\n",
     "is_executing": true
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "inverse_heights = numpy.max(rematch_distance) - rematch_distance\n",
    "inverse_heights[test_mask_rot] = 0\n",
    "# inverse_heights[~test_mask] = 200\n",
    "\n"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%% 3d bar plot\n",
     "is_executing": true
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "inverse_heights_scaled = (inverse_heights - numpy.mean(inverse_heights))/numpy.std(inverse_heights)\n"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n",
     "is_executing": true
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "inverse_heights_image = cv2.applyColorMap(((1-inverse_heights/numpy.max(inverse_heights))*255).astype(numpy.uint8), cv2.COLORMAP_JET)\n",
    "plt.imshow(inverse_heights_image)\n",
    "\n",
    "inverse_heights_image_reverse = cv2.applyColorMap(((inverse_heights/numpy.max(inverse_heights))*255).astype(numpy.uint8), cv2.COLORMAP_JET)\n",
    "cv2.imwrite(\"figures/fig7/inverse_heights_\" + policy_name + \".png\", inverse_heights_image_reverse)\n",
    "# cv2.imwrite(\"figures/fig7/inverse_heights_stack.png\", inverse_heights_image_reverse)\n",
    "# cv2.imwrite(\"figures/fig7/inverse_heights_unstack.png\", inverse_heights_image_reverse)\n",
    "# cv2.imwrite(\"figures/fig7/inverse_heights_square.png\", inverse_heights_image_reverse)\n",
    "blended = cv2.addWeighted(inverse_heights_image_reverse, 0.5, test_color_heightmap, 0.5, 0)\n",
    "cv2.imwrite(\"figures/fig7/inverse_heights_blended_\" + policy_name + \".png\", blended)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "# inverse_heights_softmax = numpy.log(numpy.exp(inverse_heights_scaled)/numpy.sum(numpy.exp(inverse_heights_scaled)))\n",
    "inverse_heights_softmax = scipy.special.softmax(inverse_heights_scaled)\n",
    "inverse_heights_softmax = (inverse_heights_softmax - numpy.min(inverse_heights_softmax))/(numpy.max(inverse_heights_softmax-numpy.min(inverse_heights_softmax)))\n",
    "inverse_heights_image_softmax = cv2.applyColorMap(((inverse_heights_softmax/numpy.max(inverse_heights_softmax))*255).astype(numpy.uint8), cv2.COLORMAP_JET)\n",
    "cv2.imwrite(\"figures/fig7/inverse_heights_softmax_\" + policy_name + \".png\", inverse_heights_image_softmax)\n",
    "blended = cv2.addWeighted(inverse_heights_image_softmax, 0.5, test_color_heightmap, 0.5, 0)\n",
    "cv2.imwrite(\"figures/fig7/inverse_heights_softmax_blended_\" + policy_name + \".png\", blended)\n",
    "inverse_heights_image_softmax = cv2.applyColorMap(((1-inverse_heights_softmax/numpy.max(inverse_heights_softmax))*255).astype(numpy.uint8), cv2.COLORMAP_JET)\n",
    "plt.imshow(inverse_heights_image_softmax)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(numpy.max(inverse_heights_softmax))\n",
    "print(numpy.min(inverse_heights_softmax))\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# inverse_heights_softmax = numpy.log(numpy.exp(inverse_heights_scaled)/numpy.sum(numpy.exp(inverse_heights_scaled)))\n",
    "inverse_heights_log_softmax = scipy.special.log_softmax(inverse_heights_scaled)\n",
    "\n",
    "inverse_heights_log_softmax = (inverse_heights_log_softmax - numpy.min(inverse_heights_log_softmax))/(numpy.max(inverse_heights_log_softmax-numpy.min(inverse_heights_log_softmax)))\n",
    "# inverse_heights_avg_softmax_log_softmax = inverse_heights_softmax + inverse_heights_log_softmax * (inverse_heights_log_softmax < 0.5)\n",
    "inverse_heights_avg_softmax_log_softmax = inverse_heights_softmax + inverse_heights_log_softmax\n",
    "inverse_heights_avg_softmax_log_softmax_image = cv2.applyColorMap(((inverse_heights_avg_softmax_log_softmax/numpy.max(inverse_heights_avg_softmax_log_softmax))*255).astype(numpy.uint8), cv2.COLORMAP_JET)\n",
    "cv2.imwrite(\"figures/fig7/inverse_heights_avg_softmax_log_softmax_\" + policy_name + \".png\", inverse_heights_avg_softmax_log_softmax_image)\n",
    "blended = cv2.addWeighted(inverse_heights_avg_softmax_log_softmax_image, 0.5, test_color_heightmap, 0.5, 0)\n",
    "cv2.imwrite(\"figures/fig7/inverse_heights_avg_softmax_log_softmax_blended_\" + policy_name + \".png\", blended)\n",
    "inverse_heights_avg_softmax_log_softmax_image = cv2.applyColorMap(((1-inverse_heights_avg_softmax_log_softmax/numpy.max(inverse_heights_avg_softmax_log_softmax))*255).astype(numpy.uint8), cv2.COLORMAP_JET)\n",
    "plt.imshow(inverse_heights_avg_softmax_log_softmax_image)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(numpy.shape(inverse_heights_avg_softmax_log_softmax))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "plt.imshow(test_color_heightmap)"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n",
     "is_executing": true
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "# Compute colors\n",
    "# colors = [test_color_heightmap[i,j,:]/255 for j in range(0, 224) for i in range(0, 224)]\n",
    "x = [i for j in range(0, 224) for i in range(0, 224)]\n",
    "y = [j for j in range(0, 224) for i in range(0, 224)]\n",
    "# top = [inverse_heights[i,j] for j in range(0, 224) for i in range(0, 224)]\n",
    "# top = [inverse_heights_softmax[i,j] for j in range(0, 224) for i in range(0, 224)]\n",
    "top = [inverse_heights_avg_softmax_log_softmax[i,j] for j in range(0, 224) for i in range(0, 224)]\n",
    "\n",
    "best_test_action_index = numpy.argmax(top)\n",
    "best_test_action = (y[best_test_action_index], x[best_test_action_index])\n",
    "# inverse_heights_image = cv2.circle(inverse_heights_image, best_test_action, 2, (255, 0, 0), 2)\n",
    "# blended = cv2.addWeighted(inverse_heights_image, 0.25, test_color_heightmap, 0.75, 0)\n",
    "# blended = cv2.addWeighted(inverse_heights_image_softmax, 0.5, test_color_heightmap, 0.5, 0)\n",
    "blended = cv2.addWeighted(inverse_heights_avg_softmax_log_softmax_image, 0.5, test_color_heightmap, 0.5, 0)\n",
    "\n",
    "\n",
    "blended = cv2.circle(blended, best_test_action, 2, (255, 0, 0), 2)\n",
    "plt.imshow(blended)\n",
    "\n",
    "colors = [blended[i,j,:]/255 for j in range(0, 224) for i in range(0, 224)]\n",
    "# _x = numpy.arange(224)\n",
    "# _y = numpy.arange(224)\n",
    "# _xx, _yy = numpy.meshgrid(_x, _y)\n",
    "# x, y = _xx.ravel(), _yy.ravel()\n",
    "\n",
    "# top = inverse_heights.flatten('C')\n",
    "bottom = numpy.zeros(len(top))"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n",
     "is_executing": true
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "figure = plt.figure(figsize=(6, 4), dpi=600)\n",
    "ax = figure.add_subplot(111, projection='3d')\n",
    "ax.view_init(elev=15., azim=90)\n",
    "# ax.view_init(elev=30., azim=0)\n",
    "\n",
    "# ax.plot_surface(numpy.array(x), numpy.array(y), numpy.array(top), color=colors)\n",
    "ax.bar3d(x, y, bottom, 1, 1, top, shade=False, color=colors)\n",
    "\n",
    "# ax.set_zlim(0, 500)\n",
    "ax.set_zlim(0, numpy.max(top)*5)\n",
    "\n",
    "plt.axis('off')\n",
    "\n",
    "plt.show()"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n",
     "is_executing": true
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "figure.savefig(\"figures/fig7/test_cc_figure_7_\" + policy_name + \".png\", transparent=True)\n",
    "# figure.savefig(\"figures/fig7/test_cc_figure_7_stack.png\", transparent=True)\n",
    "# figure.savefig(\"figures/fig7/test_cc_figure_7_unstack.png\", transparent=True)\n",
    "# figure.savefig(\"figures/fig7/test_cc_figure_7_square.png\", transparent=True)"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n",
     "is_executing": true
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "masked_test_color_heightmap = test_color_heightmap.copy()\n",
    "masked_test_color_heightmap[test_mask_rot,:] = 0\n",
    "# best_test_action = numpy.unravel_index(numpy.argmin(rematch_distance), (224, 224))\n",
    "best_test_action_index = numpy.argmax(top)\n",
    "best_test_action = (y[best_test_action_index], x[best_test_action_index])\n",
    "print(numpy.max(top))\n",
    "print(inverse_heights[best_test_action])\n",
    "print(test_mask_rot[best_test_action])\n",
    "print(best_test_action)\n",
    "\n",
    "fig, ax = plt.subplots(1)\n",
    "\n",
    "# ax.imshow(masked_test_color_heightmap)\n",
    "# ax.imshow(inverse_heights, cmap='gray')\n",
    "ax.imshow(test_color_heightmap)\n",
    "# ax.imshow(test_mask)\n",
    "ax.add_patch(Circle(best_test_action))"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n",
     "is_executing": true
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "scale_factor = 0.002\n",
    "top_scale_factor = scale_factor * 10\n",
    "points = numpy.stack((x,y,numpy.array(top)/top_scale_factor), -1) * scale_factor\n",
    "print(points.shape)\n",
    "\n",
    "rgb = (numpy.stack(colors) * 255).astype('uint8')\n",
    "\n",
    "ply = Ply(points, rgb)\n",
    "\n",
    "ply.write(\"figures/fig7/test_cc_figure_7_\" + policy_name + \".ply\")\n",
    "# ply.write(\"figures/fig7/test_cc_figure_7_stack.ply\")\n",
    "# ply.write(\"figures/fig7/test_cc_figure_7_unstack.ply\")\n",
    "# ply.write(\"figures/fig7/test_cc_figure_7_square.ply\")\n",
    "\n",
    "\n",
    "# write_xyz_rgb_as_ply(points, blended, \"figures/fig7/test_cc_figure_7_row.ply\")"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%% Save ply\n",
     "is_executing": true
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "# TODO:\n",
    "# Check rotation\n",
    "# Tighter mask - N/A\n",
    "# Subtract minimum nonzero value - done\n",
    "# Try adding the match distance\n",
    "# Save image files for figures - done\n",
    "# Refactor"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n",
     "is_executing": true
    }
   }
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "3.6.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 0
}